package ink.ykb.actDemo.service;

import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipInputStream;

import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.FlowNode;
import org.activiti.bpmn.model.SequenceFlow;
import org.activiti.engine.HistoryService;
import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.TaskService;
import org.activiti.engine.history.HistoricActivityInstance;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.activiti.engine.task.TaskQuery;
import org.activiti.image.ProcessDiagramGenerator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import ink.ykb.actDemo.controller.vo.BpmsActivityTypeEnum;
import ink.ykb.actDemo.util.UtilMisc;


@Service
@Transactional
public class WorkflowService {

	private static final String processesClasspathResource = "processes";
	@Autowired
	private ProcessEngine processEngine;
	@Autowired
	private RuntimeService runtimeService;
	@Autowired
	private TaskService taskService;
	@Autowired
	private RepositoryService repositoryService;
	@Autowired
	private HistoryService historyService;
	
	/**
	 * 通过定义好的流程图文件部署，一次只能部署一个流程
	 */
	public void deploy(String bpmnPath,String name) {
	    Deployment deployment = repositoryService.createDeployment()
	            .addClasspathResource(processesClasspathResource + bpmnPath).name(name).deploy();
	}
	/**
	 * 将多个流程文件打包部署，一次可以部署多个流程
	 */
	public void deployByZip(String bpmnPath) {
	    InputStream is = this.getClass().getClassLoader().getResourceAsStream(processesClasspathResource + bpmnPath);
	    ZipInputStream zip = new ZipInputStream(is);
	    Deployment deployment = processEngine
	            .getRepositoryService()
	            .createDeployment()
	            .addZipInputStream(zip)
	            .deploy();
	}

	/**
	 * 启动流程实例
	 * @param instanceByKey
	 * @return
	 */
	public ProcessInstance startProcessInstanceByKey(String processDefinitionKey) {
	    ProcessInstance instance = runtimeService.startProcessInstanceByKey(processDefinitionKey);
	    return instance;
	}
	public ProcessInstance startProcessInstanceByKey(String processDefinitionKey,Map<String,Object> variables) {
		ProcessInstance instance = runtimeService.startProcessInstanceByKey(processDefinitionKey, variables);
		return instance;
	}
	/**
	 * 获取当前任务列表
	 * @param assignee
	 * @param processInstanceId
	 * @return
	 */
	public List<Task> findTaskListByAssignee(String assignee,String processInstanceId) {
	    TaskQuery taskQuery = taskService.createTaskQuery();
	    if(processInstanceId != null) {
	    	taskQuery.processInstanceId(processInstanceId);
	    }
	    if(assignee != null) {
	    	 taskQuery.taskAssignee(assignee);
	    }
		List<Task> taskList = taskQuery.list();
	    return taskList;
	}
	/**
	 * 获取当前任务
	 * @param assignee
	 * @param processInstanceId
	 * @return
	 */
	public Task findTaskByAssignee(String assignee,String processInstanceId) {
		TaskQuery taskQuery = taskService.createTaskQuery();
		if(processInstanceId != null) {
			taskQuery.processInstanceId(processInstanceId);
		}
		if(assignee != null) {
			taskQuery.taskAssignee(assignee);
		}
		Task task = taskQuery.singleResult();
		return task;
	}
	
	/**
	 * 获取流程图
	 * @param procDefId
	 * @return
	 * @throws Exception
	 */
	public InputStream findProcessPic(String procDefId) throws Exception {
	    ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery().processDefinitionId(procDefId)
	            .singleResult();
	    String diagramResourceName = procDef.getDiagramResourceName();
	    InputStream imageStream = repositoryService.getResourceAsStream(procDef.getDeploymentId(), diagramResourceName);
	    return imageStream;
	}

	/**
	 * 生成流程追踪图
	 * @param processInstanceId
	 * @return
	 */
	public InputStream getResourceDiagramInputStream(String processInstanceId) {
        try {
            // 获取历史流程实例
            HistoricProcessInstance historicProcessInstance = historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
                        
            // 获取流程中已经执行的节点，按照执行先后顺序排序
//            List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().processInstanceId(processInstanceId).orderByHistoricActivityInstanceId().asc().list();
            List<HistoricActivityInstance> historicActivityInstanceList = historyService.createHistoricActivityInstanceQuery().
            		processInstanceId(processInstanceId).orderByHistoricActivityInstanceStartTime().desc().listPage(0, 1);
            
            // 构造已执行的节点ID集合
            List<String> executedActivityIdList = new ArrayList<String>();
            for (HistoricActivityInstance activityInstance : historicActivityInstanceList) {
                executedActivityIdList.add(activityInstance.getActivityId());
            }
            
            // 获取bpmnModel
            BpmnModel bpmnModel = repositoryService.getBpmnModel(historicProcessInstance.getProcessDefinitionId());
            // 获取流程已发生流转的线ID集合
            //TODO 目前获取流转多的线ID方法有问题，需要自己记录流经的路径，监听流经的线，获取线的id和前后的任务Task
//            List<String> flowIds = this.getExecutedFlows(bpmnModel, historicActivityInstanceList);
            List<String> flowIds = new ArrayList<>();
            
            // 使用默认配置获得流程图表生成器，并生成追踪图片字符流
            ProcessDiagramGenerator processDiagramGenerator = processEngine.getProcessEngineConfiguration().getProcessDiagramGenerator();
            InputStream imageStream = processDiagramGenerator.generateDiagram(bpmnModel, "png", executedActivityIdList, flowIds, "宋体", "微软雅黑", "黑体", null, 2.0);
            return imageStream;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
	
	/**
	 * 获取所有已流转的线
	 * @param bpmnModel
	 * @param historicActivityInstances
	 * @return
	 */
	private List<String> getExecutedFlows(BpmnModel bpmnModel, List<HistoricActivityInstance> historicActivityInstances) {
        // 流转线ID集合
        List<String> flowIdList = new ArrayList<String>();
        // 全部活动实例
        List<FlowNode> historicFlowNodeList = new LinkedList<FlowNode>();
        // 已完成的历史活动节点
        List<HistoricActivityInstance> finishedActivityInstanceList = new LinkedList<HistoricActivityInstance>();
        for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
            historicFlowNodeList.add((FlowNode) bpmnModel.getMainProcess().getFlowElement(historicActivityInstance.getActivityId(), true));
            if (historicActivityInstance.getEndTime() != null) {
                finishedActivityInstanceList.add(historicActivityInstance);
            }
        }
        
        // 遍历已完成的活动实例，从每个实例的outgoingFlows中找到已执行的
        FlowNode currentFlowNode = null;
        for (HistoricActivityInstance currentActivityInstance : finishedActivityInstanceList) {
            // 获得当前活动对应的节点信息及outgoingFlows信息
            currentFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(currentActivityInstance.getActivityId(), true);
            List<SequenceFlow> sequenceFlowList = currentFlowNode.getOutgoingFlows();
            
//            遍历outgoingFlows并找到已流转的
//            满足如下条件认为已已流转：
//            1.当前节点是并行网关或包含网关，则通过outgoingFlows能够在历史活动中找到的全部节点均为已流转
//            2.当前节点是以上两种类型之外的，通过outgoingFlows查找到的时间最近的流转节点视为有效流转
            FlowNode targetFlowNode = null;
            if (BpmsActivityTypeEnum.PARALLEL_GATEWAY.getType().equals(currentActivityInstance.getActivityType())
                    || BpmsActivityTypeEnum.INCLUSIVE_GATEWAY.getType().equals(currentActivityInstance.getActivityType())) {
                // 遍历历史活动节点，找到匹配Flow目标节点的
                for (SequenceFlow sequenceFlow : sequenceFlowList) {
                    targetFlowNode = (FlowNode) bpmnModel.getMainProcess().getFlowElement(sequenceFlow.getTargetRef(), true);
                    if (historicFlowNodeList.contains(targetFlowNode)) {
                        flowIdList.add(sequenceFlow.getId());
                    }
                }
            } else {
                List<Map<String, String>> tempMapList = new LinkedList<Map<String,String>>();
                // 遍历历史活动节点，找到匹配Flow目标节点的
                for (SequenceFlow sequenceFlow : sequenceFlowList) {
                    for (HistoricActivityInstance historicActivityInstance : historicActivityInstances) {
                        if (historicActivityInstance.getActivityId().equals(sequenceFlow.getTargetRef())) {
                            tempMapList.add(UtilMisc.toMap("flowId", sequenceFlow.getId(), "activityStartTime", String.valueOf(historicActivityInstance.getStartTime().getTime())));
                        }
                    }
                }
                
                // 遍历匹配的集合，取得开始时间最早的一个
                //TODO 找到匹配Flow目标节点的有问题
                long earliestStamp = 0L;
                String flowId = null;
                for (Map<String, String> map : tempMapList) {
                    long activityStartTime = Long.valueOf(map.get("activityStartTime"));
                    if (earliestStamp == 0 || earliestStamp >= activityStartTime) {
                        earliestStamp = activityStartTime;
                        flowId = map.get("flowId");
                    }
                }
                flowIdList.add(flowId);
            }
        }
        return flowIdList;
    }
	

	public  void completeTask(String taskId,Map<String, Object> variables) {
	    taskService.complete(taskId, variables);
	}
	
}
